#include "entity.hh"
#include "framework.hh"
#include "../../corelib/redis/redis.hh"
#include "../../corelib/mysql/mysql.hh"

Entity::Entity()
    : id_(INVALID_ENTITY_ID), type_(INVALID_ENTITY_TYPE), state_(INVALID_ENTITY_STATE), resolved_(false),
          pendingTimerID_(INVALID_TIMER_ID), factory_(nullptr), retryResolveTimes_(0), disposed_(false),
              identified_(false) {
    startPendingTimer();
}

Entity::~Entity() {
    tryStopPendingTimer();
    id_ = INVALID_ENTITY_ID;
    state_ = INVALID_ENTITY_STATE;
    pendingTimerID_ = INVALID_TIMER_ID;
    handlerMap_ = nullptr;
    resolved_ = false;
    identified_ = false;
    disposed_ = false;
    factory_ = nullptr;
}

void Entity::dispose() {
    if (disposed_) {
        return;
    }
    disposed_ = true;
    tryStopPendingTimer();
    EntityManagerRef.destroyEntity(this);
}

bool Entity::isDisposed() {
    return disposed_;
}

Entity::operator bool() {
    return (!disposed_);
}

EntityID Entity::getID() const {
    return id_;
}

EntityType Entity::getType() const {
    return type_;
}

void Entity::setType(EntityType type) {
    type_ = type;
}

void Entity::setState(EntityState state) {
    state_ = state;
}

EntityState Entity::getState() const {
    return state_;
}

bool Entity::resolve(EntityID entityID) {
    if (entityID == INVALID_ENTITY_ID) {
        errorlog << "Resolving entity but entity ID is invalid" << endlog;
        return false;
    }
    if (resolved_) {
        errorlog << "Resolving entity but entity is resolved, entity ID:" << entityID << endlog;
        return false;
    }
    id_ = entityID; 
    if (EntityManagerRef.addEntity(this)) {
        identified_ = true;
        resolved_ = onResolve();
        if (resolved_) {
            // Resolved
            tryStopPendingTimer();
        }
        return resolved_;
    }  else {
        errorlog << "Resolve entity failed, EntityType:" << getType() << ", EntityID:" << getID() << endlog;
        return false;
    }
}

bool Entity::onResolve() {
    return true;
}

void Entity::setFactory(EntityFactoryBase * factory) {
    factory_ = factory;
}

EntityFactoryBase * Entity::getFactory() {
    return factory_;
}

void Entity::startPendingTimer() {
    // The entity must be resolved in MAX_PENDING_TIMEOUT millionseconds
    pendingTimerID_ = ModuleRef.startTimer(MAX_PENDING_TIMEOUT,
        [&, this](TimerHandle, UserData) {
            if (resolved_) {
                return;
            }
            retryResolveTimes_ += 1;           
            if (retryResolveTimes_ > MAX_EX_TIMES) {
                // destory it
                warnlog << "Entity wasn't resolve and timeout" << endlog;
                dispose();
            } else {
                resolve();
            }
        }
    );
}

void Entity::resolve() {
    if (resolved_) {
        return;
    }
    if (identified_) {
        // In entity manager
        resolved_ = onResolve();
    } else {
        // Not in entity manager, Resovle agine
        if (id_ != INVALID_ENTITY_ID) {
            resolve(id_);
        }
    }
    if (resolved_) {
        tryStopPendingTimer();
    }
}

void Entity::tryStopPendingTimer() {
    if (pendingTimerID_ != INVALID_TIMER_ID) {
        ModuleRef.cancelTimer(pendingTimerID_);
        pendingTimerID_ = INVALID_TIMER_ID;
    }
}

bool Entity::isResolved() {
    return resolved_;
}

void EntityManager::update() {
    for (auto entity : deadEntityList_) {
        // Destroy all disposed entity in one loop
        auto factory = entity->getFactory();
        if (factory) {
            factory->destroyEntity(entity);
        } else {
            errorlog << "Memory leak, Destroy entity but not found factory, entity ID:" << entity->getID()
                << ", entity type:" << entity->getType() << endlog;
        }
    }
    if (!deadEntityList_.empty()) {
        deadEntityList_.clear();
    }
}

bool EntityManager::addEntity(Entity * entity) {
    if (INVALID_ENTITY_ID == entity->getID()) {
        errorlog << "Adding entity but ID is invalid" << endlog;
        entity->dispose();
        return false;
    }
    auto id = entity->getID();
    auto it = entityMap_.find(id);
    if (it != entityMap_.end()) {
        // Entity will retry in pending timer callback
        return false;
    }
    entityMap_[id] = entity;    
    entityTypeMap_[entity->getType()][entity->getID()] = entity;
    return true;
}

bool EntityManager::destroyEntity(Entity * entity) {
    if (INVALID_ENTITY_ID == entity->getID()) {
        deadEntityList_.push_back(entity);
        return true;
    }
    auto it = entityMap_.find(entity->getID());
    if (it == entityMap_.end()) {
        deadEntityList_.push_back(entity);
        errorlog << "Destroy entity but not found, entity ID:" << entity->getID() << endlog;
        return false;
    } else {
        auto typeMapIt = entityTypeMap_.find(entity->getType());
        if (typeMapIt != entityTypeMap_.end()) {
            typeMapIt->second.erase(entity->getID());
        }
        deadEntityList_.push_back(entity);
        entityMap_.erase(it);
        return true;
    }
}

EntityManager::~EntityManager() {
    for (auto typeMapIt : entityTypeMap_) {
        for (auto it : typeMapIt.second) {
            kratos::corelib::dispose(it.second);
        }
    }
    for (auto entity : deadEntityList_) {
        kratos::corelib::dispose(entity);
    }
}

Entity * EntityManager::getEntity(EntityID entityID) {
    auto it = entityMap_.find(entityID);
    if (it == entityMap_.end()) {
        return nullptr;
    }
    return it->second;
}

const EntityMap & EntityManager::getEntities(EntityType entityType) {
    static EntityMap NullMap;
    auto it = entityTypeMap_.find(entityType);
    if (it == entityTypeMap_.end()) {
        return NullMap;
    }
    return it->second;
}

AddressEntity::AddressEntity() {
}

AddressEntity::~AddressEntity() {
    if (isResolved()) {
        ModuleRef.getFramework()->unregisterEntity(getID());
    }
}

bool AddressEntity::onResolve() {
    // Registers to locator
    if (getID() == INVALID_ENTITY_ID) {
        return false;
    }
    return ModuleRef.getFramework()->registerEntity(getID(), getType());
}

bool AddressEntity::talk(EntityID other, const google::protobuf::Message & msg) {
    if (other == getID()) {
        // DONOT talk to yourself!
        warnlog << "Talk to self, entity ID:" << other << endlog;
        return false;
    }
    // Requests locator to relay 'msg'
    return ModuleRef.getFramework()->talkEntity(getID(), other, msg);
}
